Chapter 9
Printing

by Michael Morrison

In This Chapter

  Printing Fundamentals 346
  Printing with MFC 347
  Printing and GDI Mapping Modes 352
  WYSIWYG Printing 354
  Pagination 361
  Stopping and Aborting Print Jobs 367

Printing is a significant part of most document-centric applications because most users require a hard copy of their work at some point. Although the “paperless office” has received a lot of hype, in reality it isn’t as easy giving up paper as many people would like to believe. So, for the foreseeable future at least, printing is still something programmers have to support in most applications. Fortunately, MFC provides solid support for printing within the document/view architecture that removes a lot of the burden of printing from the programmer.

This chapter tackles printing in MFC and how it affects the design and construction of MFC applications. You’ll learn about the MFC classes used to support printing and how to use them to build WYSIWYG (what you see is what you get) applications.

Printing Fundamentals

In Chapter 4, “Painting, Device Contexts, Bitmaps, and Fonts,” you learned how MFC employs a device context to provide an abstraction for a drawing surface. This abstraction enables you to draw graphics to virtually any graphical output device with a suitable Windows graphics driver. Printing using MFC is handled in a similar manner. Printing to a printer is little different from drawing to the screen—in both cases a device context is used to abstract the drawing process. From the perspective of Windows, a printer is considered just another graphical output device, and therefore requires a graphics driver in order to be used in Windows.

It isn’t just coincidental that printers are handled no differently than monitors. It is a very beneficial part of the design of Windows to enable developers to draw graphics to a printer using the same code that they use to draw graphics to the screen. As an example, consider a graphical application such as the Paint application that ships with Windows 98. This application could use practically the same code to render a drawing to the printer as it does to render a drawing to the screen. On the other hand, adding printing support to form-based applications isn’t so simple, because they rely on child controls as the basis for their user interface. In this case, it is up to the programmer to implement a custom printing solution. Even so, you can still use familiar Windows GDI operations and an MFC device context object to carry out the printing.

Speaking of supporting printing in an application, you’re probably aware of the fact that print preview has become a standard feature in many applications. Print preview is a visualization of a document on the screen as it will appear on the printed page. You can think of print preview as a simulated print to a special preview window. In fact, it is common to use the same code in print preview as in the actual printing. MFC provides specific support for print preview in addition to its printing support.

Printing with MFC

Before the days of MFC, you had to use Win32 functions to carry out the arduous task of printing. Trust me—adding printing support to an application using straight Win32 functions was a hassle at best, and in many cases could turn into a nightmare. The problem wasn’t that drawing graphics to the printer is difficult but that it required myriad Win32 function calls to move the process along. There were also complex data structures that you had to initialize and use properly. And last but not least, you were responsible for implementing a special modeless dialog box that allowed the user to cancel out of printing a lengthy document.

MFC has simplified the process of printing significantly, thanks primarily to its document/view architecture. As a matter of fact, MFC’s document/view architecture provides you with default support for printing without your having to do any additional work. By default, MFC will use the OnDraw() member function in your view class to print a document. If you recall, OnDraw() accepts a pointer to a CDC object as its only parameter. During the printing process, this CDC object represents a printer device context instead of a screen device context.

Of course, in most cases you will want to expand on MFC’s default printing support and add print features such as headers and footers, along with pagination of documents. MFC provides a printing framework of virtual member functions that makes it easy to add exactly the functionality you need. You can add full-featured printing support to an application, including print preview, by simply overriding appropriate member functions in a view class.

Printing in the View

The majority of MFC’s printing support is encapsulated in the CView class, which means that all document/view applications have some form of default printing. There is also a set of special printing member functions in CView that you can use to alter the way in which documents are printed. For example, you must alter the default CView printing functionality if you want to print multiple pages or if you want to print a header or footer on a page.

Table 9.1 lists the most important CView member functions used for printing.

Table 9.1 The Most Important CView Member Functions Used for Printing

Member Function Description

OnPreparePrinting() Called before a document is printed or previewed
DoPreparePrinting() Displays the Print dialog box and creates a printer device context (DC); called from OnPreparePrinting()
OnBeginPrinting() Called when a print job begins; allocates print-related GDI resources
OnPrepareDC() Called before OnDraw() to prepare a DC for drawing
OnPrint() Called to print or preview a document page
OnEndPrintPreview() Called when the user exits preview mode
OnEndPrinting() Called when a print job ends; frees print-related GDI resources



The member functions listed in the table represent a printing sequence that is common across all document/view applications. Figure 9.1 shows how this process works.


Figure 9.1  The CView member functions called in the standard MFC printing sequence.

The printing process begins when the user requests to print a document. The framework calls the OnPreparePrinting() member function to display the Print dialog box and get things started. OnPreparePrinting() carries out this request by delegating the work to the DoPreparePrinting() function, which actually handles displaying the Print dialog box and creating a printer DC. You can alter the default values displayed in the Print dialog box by overriding the OnPreparePrinting() function and altering the CPrintInfo object that is passed into DoPreparePrinting(). You learn more about the CPrintInfo object in a moment.

Getting back to the print sequence, the framework calls the OnBeginPrinting() member function at the beginning of a print or print preview job, after OnPreparePrinting() has been called. The main purpose of OnBeginPrinting() is to provide a convenient place to allocate any GDI resources specifically required for printing, such as fonts. After the OnBeginPrinting() function returns, printing commences one page at a time.

The OnPrepareDC() and OnPrint() member functions are called for each page that is printed. OnPrepareDC() is responsible for making any modifications to the device context required to print the current page. It is also used to analyze attributes of the CPrintInfo object, such as the number of pages in the document. If a document length isn’t specified, OnPrepareDC() assumes the document is one page long and stops the printing sequence after one page.

The OnPrint() member function is called just after OnPrepareDC(), and is used to perform any graphical output specific to printing. Many applications call the OnDraw() function from OnPrint() to print the document as it appears in the view. These applications typically use OnPrint() to print page elements such as headers and footers. Other applications might use OnPrint() to print a document completely independent of the OnDraw() function. These applications typically have a view whose OnDraw() function isn’t helpful for printing. An example of such an application would be one that uses CFormView, which is a view containing controls based on a dialog resource.

The OnEndPrinting() member function is called at the end of a print job or print preview to free any GDI resources allocated for printing. These resources are typically allocated in the OnBeginPrinting() function. If the print sequence was issued for a print preview instead of an actual print job, the OnEndPrintPreview() function is called just before OnEndPrinting(). The default OnEndPrintPreview() function actually calls OnEndPrinting() after destroying the view window and restoring the application window to its original state.

Now that you have an idea how a view fits into the print sequence, let’s clarify exactly what an application’s view is responsible for in terms of printing. An application’s view class must take on the following responsibilities to support printing:

  Inform the framework of how many pages are in the document (or accept the default of one page).
  Allocate and free any GDI resources required for printing.
  When asked to print a specific page, draw that portion of the document.

This might seem like relatively little work to support printing in an application’s view class. That’s because MFC does a lot of the work for you. More specifically, the MFC framework must take on the following responsibilities to support printing:

  Display the Print dialog box
  Create a suitable CDC object for the printer
  Inform the view class of which page to print
  Call CView printing member functions at the appropriate times

The PrintInfo Object

You’ve encountered the CPrintInfo object a few times in the previous discussion on the CView member functions associated with printing. The CPrintInfo object maintains information about a print or print preview job. You don’t ever need to create a CPrintInfo object yourself—the framework automatically creates it when the print sequence begins.

The CPrintInfo object contains information such as the range of pages to be printed and the current page being printed. This information is accessible through public data members of the CPrintInfo class. Following are the most commonly used public data members in the CPrintInfo class:

  m_nCurPage—Identifies the page currently being printed
  m_nNumPreviewPages—Identifies the number of pages displayed in the print preview (1 or 2)
  m_bPreview—Indicates whether the document is being previewed
  m_bDirect—Indicates whether the document is being printed directly (bypassing the Print dialog box)
  m_rectDraw—Specifies the usable page area for printing

The first two members, m_nCurPage and m_nNumPreviewPages, are useful for controlling the printing of a multiple-page document. The m_bPreview member is used to determine whether the document is being printed to the printer or displayed in a print preview window. Finally, the m_rectDraw member contains a rectangle that represents the usable page area for printing. You will typically shrink this rectangle to reduce the page area available for printing, which makes room for headers and footers on the page.

The CPrintInfo object serves as a means of exchanging information between an application’s view and MFC’s built-in printing functionality. A CPrintInfo object is passed between the framework and your view class during the printing process. As an example, your view class knows which page to print because the framework sets the m_nCurPage member of CPrintInfo.

Printing Menu Commands

MFC provides a set of standard identifiers that represent menu commands associated with printing. These command identifiers are extremely useful when creating a user interface for printing from an application. Following are the standard print command identifiers defined by MFC:

  ID_FILE_PRINT_SETUP
  ID_FILE_PRINT
  ID_FILE_PRINT_DIRECT
  ID_FILE_PRINT_PREVIEW

MFC provides default message handler implementations for each of these commands. The CWinApp::OnFilePrintSetup() message handler for the ID_FILE_PRINT_SETUP command invokes the standard print setup dialog that allows the user to alter the printer settings. All you must do to include this functionality in an application is provide the following message map entry in your application class:

ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

The CView::OnFilePrint() message handler for the ID_FILE_PRINT command calls the OnPreparePrinting() function to display the standard Print dialog and create the printer DC. For each page, it calls OnPrepareDC() followed by a call to OnPrint() for that page. When the print job finishes, the OnEndPrinting() function is called, and the printing progress dialog is closed. The ID_FILE_PRINT_DIRECT command uses the OnFilePrint() message handler to print without first displaying the Print dialog box; the default printer and related settings are used. The ID_FILE_PRINT_DIRECT command is typically reserved for use with a toolbar Print button.



The CView::OnFilePrintPreview() message handler for the ID_FILE_PRINT_PREVIEW command initiates the print preview of a document. You don’t have to do anything but provide a message map entry for this command to support a default print preview. Following are the view class message map entries required to include the functionality of the ID_FILE_PRINT, ID_FILE_PRINT_DIRECT, and ID_FILE_PRINT_PREVIEW commands in an application:

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

Printing and GDI Mapping Modes

Because printers are inherently different graphical output devices than monitors, you must make sure that GDI operations performed on a printer device context yield consistent results as if they were performed on a screen device context. What I’m getting at is that printers have different resolutions than monitors, which means that you must use a GDI mapping mode that doesn’t depend on physical device coordinates.

By default, GDI operations use the MM_TEXT mapping mode, which performs a one-to-one mapping of physical device units to logical units. In other words, logical and physical units are equivalent in the MM_TEXT mapping mode. Furthermore, the coordinate system in this mapping mode increases down and to the right from the upper-left origin in a window.

Consider the ramifications of this mapping mode on the resolutions of monitors and printers. You probably are using a monitor with the resolution set at 800×600, 1024×768, 1152×864, or 1280×1024. These numbers reflect the number of individual pixels in the x and y directions, respectively. Now consider the resolution of a laser printer. Laser printers all have resolutions of 300dpi or greater. That means that there are 300 printer pixels for every inch of page. That means that an 8 1/2×11 inch page has a resolution of 2550&#×3300 pixels. If you were to print graphics on a 300dpi laser printer using the MM_TEXT mapping mode, they would be displayed at around 45% of their screen size. Figure 9.2 illustrates the problem with the MM_TEXT mapping mode.


Figure 9.2  Graphics printed using the MM_TEXT mapping mode will appear smaller on the printed page than they do on the screen.

The source of the problem is the MM_TEXT mapping mode, which doesn’t take into account the differences in hardware device resolutions. As long as you’re mapping logical units on a one-to-one basis with hardware pixels, you’re guaranteed to get inconsistent results on different types of hardware. The solution is to use a mapping mode that doesn’t think in terms of pixels.

Win32 supports a few different mapping modes that are suitable for printing. One of these is MM_LOENGLISH, which maps a logical unit to a 0.01-inch physical unit. In other words, 100 logical units would appear as 1 inch regardless of the physical device. This functionality is expected in modern applications and is sometimes referred to as WYSIWYG. Figure 9.3 illustrates how the MM_LOENGLISH mapping mode solves the WYSIWYG problem.


Figure 9.3  The MM_LOENGLISH mapping mode results in graphics appearing the same in print as they do on the screen.


Note:  

The y-axis of the MM_LOENGLISH coordinate system increases in the opposite direction of MM_TEXT. This means that y values decrease down from the origin in the upper-left corner of a window, which results in negative y coordinate values. This sometimes requires special handling because most applications aren’t accustomed to dealing with negative graphics coordinates.


WYSIWYG Printing

Now that you have an idea as to how printing works in MFC, let’s take a look at a practical example. I built an application called Shaper that allows you to draw primitive graphic shapes including lines, rectangles, triangles, and ellipses. All of these shapes are drawn using standard GDI operations invoked through the CDC class. This is a good sample application for printing because it is necessary to print the shapes accurately so that they appear the same on the printer as they do on the screen. If you recall, this is known as WYSIWYG, and is an important requirement of most graphical applications that support printing.

In addition to the code that specifically supports WYSIWYG printing, there are also some other areas of the Shaper application that affect its printing capability. This chapter touches on all of these areas in order to show you how a real application supports printing. Let’s begin with the application’s resources.

Application Resources

It isn’t possible to print from an application if the user interface doesn’t provide print-related menu commands. So it is necessary to include print menu commands in the Shaper application’s menu. Listing 9.1 contains the menu resource for Shaper.

Listing 9.1 The Menu Resource Definition for Shaper


IDR_SHAPER MENU
BEGIN
  POPUP “&File”
    BEGIN
      MENUITEM “&New\tCtrl+N”,          ID_FILE_NEW
      MENUITEM “&Open...\tCtrl+O”,      ID_FILE_OPEN
      MENUITEM “&Save\tCtrl+S”,         ID_FILE_SAVE
      MENUITEM “Save &As...”,           ID_FILE_SAVE_AS
      MENUITEM SEPARATOR
      MENUITEM “&Print...\tCtrl+P”,     ID_FILE_PRINT
      MENUITEM “Print Pre&view”,        ID_FILE_PRINT_PREVIEW
      MENUITEM “P&rint Setup...”,       ID_FILE_PRINT_SETUP
      MENUITEM SEPARATOR
      MENUITEM “Recent File”,           ID_FILE_MRU_FILE1,GRAYED
      MENUITEM SEPARATOR
      MENUITEM “E&xit”,                 ID_APP_EXIT
    END
  POPUP “&Edit”
    BEGIN
      MENUITEM “Clear &All”,            ID_EDIT_CLEAR_ALL
    END
  POPUP “&Draw”
    BEGIN
      MENUITEM “&Line”,                 ID_DRAW_LINE
      MENUITEM “&Rectangle”,            ID_DRAW_RECTANGLE
      MENUITEM “&Triangle”,             ID_DRAW_TRIANGLE
      MENUITEM “&Ellipse”,              ID_DRAW_ELLIPSE
      MENUITEM SEPARATOR
      MENUITEM “Change C&olor...”       ID_DRAW_CHANGECOLOR
    END
  POPUP “&View”
    BEGIN
      MENUITEM “&Toolbar”,              ID_VIEW_TOOLBAR
      MENUITEM “&Status Bar”,           ID_VIEW_STATUS_BAR
    END
END

The IDR_SHAPER menu defines the following important print menu commands, along with their associated standard command identifiers:

  File, Print—ID_FILE_PRINT
  File, Print Preview—ID_FILE_PRINT_PREVIEW
  File, Print Setup—ID_FILE_PRINT_SETUP

To provide a more complete user interface for the Shaper application, it is good to include a toolbar that provides access to commonly used menu commands. A single Print button is sufficient for allowing the user to print using the toolbar. Figure 9.4 shows a zoomed view of the toolbar bitmap image for the Shaper application, which includes a Print button.


Figure 9.4  A zoomed view of the toolbar bitmap image for Shaper.

Listing 9.2 contains the toolbar resource for the Shaper application, which associates a print command identifier with the Print toolbar button.

Listing 9.2 The Toolbar Resource Definition for Shaper


IDR_SHAPER TOOLBAR  16, 15
BEGIN
  BUTTON      ID_FILE_NEW
  BUTTON      ID_FILE_OPEN
  BUTTON      ID_FILE_SAVE
  BUTTON      ID_FILE_PRINT_DIRECT
  SEPARATOR
  BUTTON      ID_EDIT_CLEAR_ALL
  SEPARATOR
  BUTTON      ID_DRAW_LINE
  BUTTON      ID_DRAW_RECTANGLE
  BUTTON      ID_DRAW_TRIANGLE
  BUTTON      ID_DRAW_ELLIPSE
  BUTTON      ID_DRAW_CHANGECOLOR
END



Notice that the command ID_FILE_PRINT_DIRECT is used for the Print button instead of ID_FILE_PRINT. This is done so that the button will print a document without displaying the Print dialog box, which is a little quicker for the user. This also happens to be the standard approach taken by most Windows applications.

Another way to make things quicker for the user is to support a print accelerator. Listing 9.3 contains the keyboard accelerator resources for the Shaper application, which defines a print accelerator.

Listing 9.3 The Keyboard Accelerator Resource Definitions for Shaper


IDR_SHAPER ACCELERATORS
BEGIN
  “N”,            ID_FILE_NEW,          VIRTKEY,CONTROL
  “O”,            ID_FILE_OPEN,         VIRTKEY,CONTROL
  “S”,            ID_FILE_SAVE,         VIRTKEY,CONTROL
  “P”,            ID_FILE_PRINT,        VIRTKEY,CONTROL
END

That wraps up the print-related resources required for the Shaper application. Let’s move on to the application code.

The Application Class

You might not expect the application class to have anything to do with printing. In truth, it doesn’t play much of a role, but there is a small piece of code in the application class that is required to support printing in the Shaper application. More specifically, the default OnFilePrintSetup() message handler is implemented in the CWinApp class, which makes it necessary to place the ID_FILE_PRINT_SETUP menu command message handler in the CShaperApp application class. Following is the message map entry for this command:

ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

The default implementation of OnFilePrintSetup() handles all of the details of displaying the Print Setup dialog box and interpreting the user responses. Thus, supporting the print setup feature in an MFC application requires only the ID_FILE_PRINT_SETUP message map entry.

The View Class

Most of the printing support in an application takes place in the view. In the case of the Shaper application, the CShaperView class takes on much of the work of printing Shaper documents. Perhaps the best place to start in analyzing the CShaperView class is its message map, which includes entries for print-related menu commands. Listing 9.4 contains the CShaperView message map.

Listing 9.4 The CShaperView Message Map for Shaper


BEGIN_MESSAGE_MAP(CShaperView, CScrollView)
  ON_WM_LBUTTONDOWN()
  ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
  ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
  ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
  ON_COMMAND(ID_DRAW_LINE, OnDrawLine)
  ON_COMMAND(ID_DRAW_RECTANGLE, OnDrawRectangle)
  ON_COMMAND(ID_DRAW_TRIANGLE, OnDrawTriangle)
  ON_COMMAND(ID_DRAW_ELLIPSE, OnDrawEllipse)
  ON_COMMAND(ID_DRAW_CHANGECOLOR, OnDrawChangeColor)
  ON_UPDATE_COMMAND_UI(ID_DRAW_LINE, OnUpdateDrawLine)
  ON_UPDATE_COMMAND_UI(ID_DRAW_RECTANGLE, OnUpdateDrawRectangle)
  ON_UPDATE_COMMAND_UI(ID_DRAW_TRIANGLE, OnUpdateDrawTriangle)
  ON_UPDATE_COMMAND_UI(ID_DRAW_ELLIPSE, OnUpdateDrawEllipse)
END_MESSAGE_MAP()

Three of these message map entries are associated with printing: ID_FILE_PRINT, ID_FILE_PRINT_DIRECT, and ID_FILE_PRINT_PREVIEW. As you can see, all three of these message map entries route messages to existing CView message handlers.

MFC’s printing architecture doesn’t require you to provide application-specific message handlers for any printing commands. Instead, you are expected to override other CView member functions that perform specific printing operations. You learned about these member functions earlier in the chapter. Following is the declaration of one of them in the CShaperView class:

virtual BOOL  OnPreparePrinting(CPrintInfo* pInfo);

If you recall, the OnPreparePrinting() member function is called by the CView::OnFilePrint() message handler to initiate the printing process. Following is the implementation of the OnPreparePrinting() member function in CShaperView, which simply calls DoPreparePrinting() to delegate the work of starting the printing process:

BOOL CShaperView::OnPreparePrinting(CPrintInfo* pInfo) {
  return DoPreparePrinting(pInfo);
}

You learned earlier in the chapter that the MM_TEXT mapping mode is problematic for printing because physical device coordinates differ between the monitors and printers. For this reason, the Shaper application uses the MM_LOENGLISH mapping mode, which results in graphics being drawn consistently across all graphical output devices. The mapping mode is set in the call to SetScrollSizes() in the OnInitialUpdate() member function, which follows:

void CShaperView::OnInitialUpdate() {
  // Set the scroll sizes
  CShaperDoc* pDoc = GetDocument();
  ASSERT_VALID(pDoc);
  SetScrollSizes(MM_LOENGLISH, pDoc()->GetDocSize());
}

The other place in the view where the SetScrollSizes() member function is called is in the OnUpdate() member function. Listing 9.5 contains the source code for the OnUpdate() member function.

Listing 9.5 The CShaperView::OnUpdate() Member Function for Shaper


void CShaperView::OnUpdate(CView* pSender, LPARAM lHint,
  CObject* pHint) {
  // Make sure the hint is valid
  if (pHint != NULL) {
    if (pHint->IsKindOf(RUNTIME_CLASS(CShape))) {
      // Update the scroll sizes
      CShaperDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      SetScrollSizes(MM_LOENGLISH, pDoc->GetDocSize());

      // Invalidate only the rectangular position of the new shape
      CShape* pShape = DYNAMIC_DOWNCAST(CShape, pHint);
      CClientDC dc(this);
      OnPrepareDC(&dc);
      CRect rc = pShape->GetPosition();
      dc.LPtoDP(&rc);
      rc.InflateRect(1, 1);
      InvalidateRect(&rc);
      return;
    }
  }
  // Invalidate the entire view
  Invalidate();
}r

The OnDraw() member function is called to draw both to the screen and the printer and must draw graphics properly using the MM_LOENGLISH mapping mode. More specifically, OnDraw() has to deal with the issue of the MM_LOENGLISH mapping mode’s y-axis increasing in the negative direction. Listing 9.6 contains the source code for the OnDraw() member function.

Listing 9.6 The CShaperView::OnDraw() Member Function for Shaperr


void CShaperView::OnDraw(CDC* pDC) {
  // Get a pointer to the document
  CShaperDoc* pDoc = GetDocument();
  ASSERT_VALID(pDoc);

  // Get the clipping rect for the DC
  CRect rcClip, rcShape;
  pDC->GetClipBox(&rcClip);
  rcClip.top = -rcClip.top;
  rcClip.bottom = -rcClip.bottom;

  // Draw the view (paint the shapes)
  POSITION pos = pDoc->m_shapeList.GetHeadPosition();
  while (pos != NULL) {
    // Get the next shape
    CShape* pShape = pDoc->m_shapeList.GetNext(pos);

    // Only draw if the shape rect intersects the clipping rect
    rcShape = pShape->GetPosition();
    rcShape.top = -rcShape.top;
    rcShape.bottom = -rcShape.bottom;
    if (rcShape.IntersectRect(&rcShape, &rcClip))
      pShape->Draw(pDC);
  }
}



The solution to the negative y-axis problem in OnDraw() is to negate the y components of each rectangle in the code. Notice that the top and bottom members of the rcClip and rcShape rectangles are both negated before the rectangles are used. This results in positive values for the y components of the rectangles. Unless you make this change, the IntersectRect() function would have trouble interpreting the negative rectangle components, and would never detect a rectangle intersection.

By the way, the CShape class represents a simple shape and includes the constant shape identifiers LINE, RECTANGLE, TRIANGLE, and ELLIPSE. The document maintains a list of CShape objects in the m_shapeList member variable. When drawing shapes from the list, the OnDraw() member function checks to see if the bounding rectangles for each shape intersect the clipping rectangle, and draws only shapes that intersect it. This makes the drawing of shapes much more efficient.

You might feel that I’m straying a bit from the topic of printing with all this shape talk. However, I’m trying to show you how printing isn’t just about margins and page counts; printing affects many parts of an application. Let’s take a look now at how the view specifically supports printing.

If you recall from earlier in the chapter, the OnPrint() member function is responsible for performing any additional drawing when a document is being printed. Following is the declaration of OnPrint() in the CShaperView class:

virtual void  OnPrint(CDC* pDC, CPrintInfo* pInfo);

Listing 9.7 contains the source code for the OnPrint() member function.

Listing 9.7 The CShaperView::OnPrint() Member Function for Shaper


void CShaperView::OnPrint(CDC* pDC, CPrintInfo* pInfo) {
  // Get a pointer to the document
  CShaperDoc* pDoc = GetDocument();
  ASSERT_VALID(pDoc);

  // Print the page header and adjust the DC window origin
  CString sDocTitle = pDoc->GetTitle();
  PrintPageHeader(pDC, pInfo, sDocTitle);
  pDC->SetWindowOrg(pInfo->m_rectDraw.left, -pInfo->m_rectDraw.top);

  // Print the document data
  OnDraw(pDC);
}

The OnPrint() member function takes on the task of printing the header on the page before allowing OnDraw() to draw the actual document data. Notice how the window origin of the device context is altered to reflect the drawing rectangle maintained by the CPrintInfo object. This is necessary so that the OnDraw() function can’t draw in the header; in fact, OnDraw() doesn’t even know about the header thanks to the shrunken drawing rectangle.r

One member function that definitely does know about the header is PrintPageHeader(), which is called by OnPrint(). The PrintPageHeader() member function is responsible for printing the document header. Following is the declaration of the PrintPageHeader() member function:

void PrintPageHeader(CDC* pDC, CPrintInfo* pInfo, CString& sHeader);

The PrintPageHeader() member function accepts a string as its third parameter and prints it, along with a horizontal line below the string that goes across the page. Listing 9.8 contains the source code for the PrintPageHeader() member function.

Listing 9.8 The CShaperView::PrintPageHeader() Member Function for Shaperr


void CShaperView::PrintPageHeader(CDC* pDC, CPrintInfo* pInfo,
  CString& sHeader) {
  // Draw the header text aligned left
  pDC->SetTextAlign(TA_LEFT);
  pDC->TextOut(0, -25, sHeader);

  // Draw a line across the page just below the header text
  TEXTMETRIC tm;
  pDC->GetTextMetrics(&tm);
  int y = -35 - tm.tmHeight;
  pDC->MoveTo(0, y);
  pDC->LineTo(pInfo->m_rectDraw.right, y);

  // Adjust the drawing rect to not include the header
  y -= 25;
  pInfo->m_rectDraw.top += y;
}

The header text is drawn in PrintPageHeader() using a negative y coordinate, which is necessary when working within the MM_LOENGLISH mapping mode. The value of -25 equates to 1/4 inch in this mapping mode because each logical unit is equivalent to 0.01 inch (25 × 0.01=0.25). A line is drawn across the page just below the text, after which the PrintPageHeader() function adjusts the drawing rectangle to exclude the header.

Figure 9.5 shows the completed Shaper application. I encourage you to try out the application and print a few documents to see how closely the printed page matches the screen. You should also try out the print preview feature, which is shown in Figure 9.6.

Pagination

The default printing functionality built into MFC is designed to support single-page printing. MFC certainly doesn’t prevent you from printing multiple pages, but you can’t rely solely on its default functionality if you are printing a multiple-page document. Even so, printing multiple pages isn’t too difficult, and MFC definitely makes the task easier than the old Win32 API approach.


Figure 9.5  The completed Shaper application.


Figure 9.6  The Print Preview window in the Shaper application.

When it comes to printing multiple-page documents, it is very important whether the number of pages can be determined prior to printing. If the page count can be determined in advance, you can use a simpler approach to establish a print loop that iterates through the pages. If it isn’t possible to calculate the page count in advance, you can still print multiple pages, but it requires a little more work.

Printing with a Known Page Count

If you know the page count in advance, establishing the print loop is as simple as calling the SetMaxPage() member function on the CPrintInfo object passed into the OnPreparePrinting() function. This sets the number of pages in the print job, which directly affects the number of pages printed in the print loop. Listing 9.9 contains an example of an OnPreparePrinting() function that sets the number of pages for a print job using SetMaxPage().

Listing 9.9 An OnPreparePrinting() Member Function that Sets the Page Count


BOOL CMyView::OnPreparePrinting(CPrintInfo* pInfo) {
  // Set the number of pages in the print job
  CMyDoc* pDoc = GetDocument();
  ASSERT_VALID(pDoc);
  int nPages = pDoc->CalcNumPages();
  pInfo->SetMaxPage(nPages);

  return DoPreparePrinting(pInfo);
}

This code assumes that the document class, CMyDoc, defines a member function named CalcNumPages() that calculates the number of print pages based on the document data. You could also implement a similar function in the view class because the number of pages is arguably an attribute of the view and not the document. However, a document such as a word processor document would maintain its own page breaks and would therefore probably have knowledge of the page count directly in the document class.



Regardless of where the page count is calculated, it enters the printing picture when the SetMaxPage() member function is called in OnPreparePrinting(). Keep in mind that the OnPrint() function takes on the responsibility of printing the appropriate information based on the page being printed. It is passed a CPrintInfo object that it can use to obtain information about the current page. For example, the m_nCurPage member variable contains the page number of the page currently being printed. You can also determine the first and last pages in the range of pages being printed by calling the GetFromPage() and GetToPage() member functions.

Printing with an Unknown Page Count

It’s easy enough to support pagination when you know the page count in advance. However, pagination is a little trickier when the page count is an unknown. You might be wondering how it could be possible that you wouldn’t know the page count prior to invoking a print job in an application. However, consider as an example a database application that must perform a query in order to print a series of records. This type of application would typically retrieve and print records obtained from a database query during the print process, which makes it impossible to know in advance how many pages there are to be printed.

Printing a document with an unknown page count is referred to as on-the-fly pagination, because you are effectively calculating the page count on-the-fly as the print job proceeds. Instead of setting the page count using the SetMaxPage() member function, you inform MFC that you aren’t finished printing as long as there is more data to be printed. This is taken care of in the OnPrepareDC() function, which is called before each page is printed.

To inform MFC that there is more to be printed, you set the m_bContinuePrinting member variable in the CPrintInfo object that is passed into OnPrepareDC(). Setting this member variable to TRUE tells MFC that it should go ahead and continue printing the next page. Listing 9.10 contains a sample OnPrepareDC() function that shows how to set this member variable in order to continue printing a document of unknown size.

Listing 9.10 An OnPrepareDC() Member Function That Prints a Document of Unknown Page Count


BOOL CMyView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) {
  CView::OnPrepareDC(pDC, pInfo);

  // Set the mapping mode
  pDC->SetMapMode(MM_LOENGLISH);

  // Continue printing until the query finishes
  CMyDoc* pDoc = GetDocument();
  ASSERT_VALID(pDoc);
  if (pInfo != NULL)
    pInfo->m_bContinuePrinting = !pDoc->QueryFinished();
}

This example shows how to dynamically control the print loop in a hypothetical database application. A function called QueryFinished() is called on the document object to determine whether the database query that controls the printing has finished. The m_bContinuePrinting member variable is set based on the return value of the QueryFinished() function.

Earlier I mentioned that MFC’s default printing functionality was geared toward printing single-page documents. This is apparent when you take a look at the source code for the default CView::OnPrepareDC() member function, which is shown in Listing 9.11.

Listing 9.11 The Default CView::OnPrepareDC() Member Function That Prints a Single Page


void CView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) {
  ASSERT_VALID(pDC);
  UNUSED(pDC); // unused in release builds

  // Default to one page printing if doc length not known
  if (pInfo != NULL)
    pInfo->m_bContinuePrinting = (pInfo->GetMaxPage() != 0xffff ||
      (pInfo->m_nCurPage == 1));
}

In the default implementation of OnPrepareDC(), the m_bContinuePrinting member variable is set to TRUE only if the current page is the first page. In other words, m_bContinuePrinting is set to FALSE after the first page of a document is printed. This results in only a single page being printed regardless of the page count. Of course, explicitly setting the page count through a call to SetMaxPage() in the OnPreparePrinting() function bypasses this functionality, which is evident in the code for CView::OnPrepareDC().

Printing Page Numbers

Regardless of how you establish the page count for a multiple-page print job, you will probably want to print the current page number on each printed page. The most logical place to print page numbers is in the footer of the page, which appears just below the body of the page.

The CPrintInfo object that is passed into the OnPrint() function provides information about the current page being printed, along with the range of pages being printed in the print job. You can use this information to print the current page number and its relationship to other pages in the print job. More specifically, the m_nCurPage member variable of CPrintInfo contains the current page number. You can determine the number of pages being printed by calling the GetToPage() and GetFromPage() member functions. Following is an example of how this number can be calculated:

int nPageCount = pInfo->GetToPage() - pInfo->GetFromPage() + 1;

As an example, consider the situation where you are printing pages 7 through 15 of a document. Because the page range is inclusive, the print job in this example consists of a total of 9 pages. The previous calculation would correctly calculate this total. Listing 9.12 contains a more complete example that shows how to print an entire page footer including the current page and the page range.

Listing 9.12 A PrintPageFooter() Member Function for Printing Page Footers


void CMyView::PrintPageFooter(CDC* pDC, CPrintInfo* pInfo) {
  // Assemble the footer string and calculate its size
  CString sFooter;
  int nPageCount = pInfo->GetToPage() - pInfo->GetFromPage() + 1;
  sFooter.Format(“Page %d of %d”, pInfo->m_nCurPage, nPageCount);
  CSize sizFooter = pDC->GetTextExtent(sFooter);

  // Draw a line separating the footer from the document body
  CRect& rcPage = pInfo->m_rectDraw;
  int nYBottom = rcPage.bottom + sizFooter.cy * 2 + 100);
  int nYCur = nYBottom;
  pDC->MoveTo(0, nYCur);
  pDC->LineTo(rcPage.right, nYCur);
  nYCur -= sizFooter.cy;

  // Draw the footer
  pDC->TextOut(rcPage.left + (rcPage.Width() - sizFooter.cx) / 2,
    nYCur,
    sFooter);

  // Adjust printable area
  rcPage.bottom = nYBottom;
}

You would call this function from the OnPrint() function just before printing the body of the document. It’s very important to print both the header and footer of a page before printing the document body because printing the header and footer requires adjustments to the drawing rectangle. Printing the header and footer before the body ensures that the body won’t be printed over them. Listing 9.13 contains an example of an OnPrint() function that prints a header, footer, and document body in the proper order.



Listing 9.13 An OnPrint() Member Function That Prints the Header, Footer, and Document Body in the Proper Order


void CMyView::OnPrint(CDC* pDC, CPrintInfo* pInfo) {
  // Print the page header and footer
  PrintPageHeader(pDC, pInfo);
  PrintPageFooter(pDC, pInfo);

  // Print the document body
  PrintPageBody(pDC, pInfo);
}

This example assumes that you aren’t printing a WYSIWYG document, which means that the OnDraw() function isn’t used for printing. That’s why the PrintPageBody() function is called instead of OnDraw(). For a WYSIWYG document, you should call OnDraw() instead of creating a PrintPageBody() function.

Stopping and Aborting Print Jobs

Although the user can stop or abort the printing process by selecting a printer from the Windows Printers folder and using the Print Manager, it is sometimes useful to halt a print job programmatically. You have two options for programmatically halting a print job:

  Stopping the print job, which stops the print job but allows pages that have already been rendered to be printed
  Aborting the print job, which stops the print job and stops all pages from being printed

Along with these two choices of how to halt a print job, you also have the option of performing the halt either in the OnPrepareDC() function or the OnPrint() function.

Halting a Print Job in OnPrepareDC()

To stop a print job from within the OnPrepareDC() function, you simply set the m_bContinuePrinting member variable to FALSE. This results in the print loop exiting, but any pages already rendered to the print spooler will continue to print. To stop all pages from printing, you must call the AbortDoc() member function on the printer device context after setting the m_bContinuePrinting member variable to FALSE. The AbortDoc() function terminates the print job and clears out any rendered pages that have yet to be printed. Listing 9.14 contains a sample OnPrepareDC() function that is capable of stopping and aborting the print job.

Listing 9.14 An OnPrepareDC() Member Function That Is Capable of Stopping and Aborting a Print Job


BOOL CMyView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) {
  CView::OnPrepareDC(pDC, pInfo);

  // Set the mapping mode
  pDC->SetMapMode(MM_LOENGLISH);

  // Stop the print job if necessary
  if (bStopPrinting || bAbortPrinting)
    if (pInfo != NULL)
      pInfo->m_bContinuePrinting = FALSE;

  // Abort the print job if necessary
  if (bAbortPrinting)
    pDC->AbortDoc();
}

This example uses two Boolean member variables named bStopPrinting and bAbortPrinting to determine whether the print job should be stopped or aborted. Presumably, you would set these member variables elsewhere in an application in response to the user canceling the print job.

Halting a Print Job in OnPrint()

The other approach to halting a print job involves the OnPrint() function. In this case, you call the EndDoc() member function on the printer device context to stop the print job, or call the AbortDoc() member function to abort the print job. Listing 9.15 contains a sample OnPrint() function that is capable of stopping and aborting a print job using EndDoc() and AbortDoc(), respectively.

Listing 9.15 An OnPrint() Member Function That Is Capable of Stopping and Aborting a Print Job


void CMyView::OnPrint(CDC* pDC, CPrintInfo* pInfo) {
  // Stop the print job if necessary
  if (bStopPrinting)
    pDC->EndDoc();

  // Abort the print job if necessary
  if (bAbortPrinting)
    pDC->AbortDoc();

  // Print the page header and footer
  PrintPageHeader(pDC, pInfo);
  PrintPageFooter(pDC, pInfo);

  // Print the document body
  PrintPageBody(pDC, pInfo);
}

This sample code uses the same bStopPrinting and bAbortPrinting member variables to determine whether the print job should be stopped or aborted. The code is very straightforward in that a call to EndDoc() or AbortDoc() is all that is required to stop or abort the print job.

Summary

This chapter introduces you to printing and how it fits into the MFC framework. You started off the chapter by learning some basics about printing, along with the main MFC classes that facilitate printing in applications based upon MFC’s document/view architecture. You then learned about the significance of mapping modes when it comes to printing, and how some mapping modes are more suited to printing than others. From there, you examined how to add printing support to a graphics application.

You then shifted gears a little and took a look at pagination and how to manage the printing of multiple-page documents. Finally, I wrapped up by explaining how to stop and abort print jobs programmatically using MFC.